﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Engine.Mathematics
{
    /// <summary>
    /// Used for work with dense matrixes.
    /// Note. All indexes are zero based.
    /// </summary>
    public class DenseMatrix
    {
        #region Fields

        /// <summary>
        /// Store non zero matrix elements
        /// </summary>
        private LinkedList<Element>[] _elements;
        /// <summary>
        /// Number of rows in matrix
        /// </summary>
        private int _rows;
        /// <summary>
        /// Number of cols in matrix
        /// </summary>
        private int _cols;

        #endregion

        #region Contructors

        /// <summary>
        /// Standart constructor that initializing this matrix
        /// </summary>
        /// <param name="rows">Number of rows in matrix</param>
        /// <param name="cols">Number of cols in matrix</param>
        public DenseMatrix(int rows, int cols)
        {
            _rows = rows;
            _cols = cols;
            _elements = new LinkedList<Element>[rows];
        }

        #endregion

        #region Indexers and Properties
        /// <summary>
        /// Gets acces to specified element in matrix
        /// </summary>
        /// <param name="i">row</param>
        /// <param name="j">column</param>
        /// <returns></returns>
        public double this[int i, int j]
        {
            get
            {
                return LocateElement(i, j);
            }

            set
            {
                StoreElement(i, j, value);
            }
        }


        public int Rows
        {
            get { return _rows; }
            set { _rows = value; }
        }

        public int Cols
        {
            get { return _cols; }
            set { _cols = value; }
        }

        #endregion

        #region Methods


        public static Vector operator *(DenseMatrix a, Vector b)
        {
            if (a._cols != b.Size) throw new ArgumentException("Number of column in Matrix a is not equals to number of elements in Vector b");
            Vector result = new Vector(b.Size);
            for (int i = 0; i < b.Size; i++)
            {
                if (a._elements[i] == null)
                {
                    result[i] = 0d; //row consist only from zero}
                }
                else
                {
                    LinkedList<Element> elementsInRow = a._elements[i];
                    double ij = 0d;
                    foreach (var element in elementsInRow)
                    {
                        ij += element.Value*b[element.ColumnIndex];
                    }
                    result[i] = ij;
                }
            }
            return result;
        }



        /// <summary>
        /// Used by indexer for storing new element at specified position
        /// </summary>
        /// <param name="i">row</param>
        /// <param name="j">column</param>
        /// <param name="value">stored value</param>
        private void StoreElement(int i, int j, double value)
        {
            if (j < 0 || j > _cols - 1) throw new ArgumentOutOfRangeException("j");
            Element element = new Element() { RowIndex = i, ColumnIndex = j, Value = value };
            if (_elements[i] == null)
            {
                _elements[i] = new LinkedList<Element>();
                _elements[i].AddFirst(element);
            }
            else
            {
                LinkedList<Element> elementsInRow = _elements[i];
                bool addNormal = false;
                foreach (Element contained in elementsInRow)
                {
                    if (contained.ColumnIndex > element.ColumnIndex)
                    {
                        LinkedListNode<Element> containedNode = elementsInRow.Find(contained);
                        elementsInRow.AddBefore(containedNode, element);
                        //get out from this cycle
                        addNormal = true;
                        break;
                    }
                    else if (contained.ColumnIndex == element.ColumnIndex)
                    {
                        addNormal = true;
                        contained.Value = element.Value;
                    }
                }
                if (!addNormal) elementsInRow.AddLast(element);
            }
        }

        private double LocateElement(int i, int j)
        {
            if (j < 0 || j > _cols - 1) throw new ArgumentOutOfRangeException("j");
            if (_elements[i] == null) return 0d; //in this row have only zero values
            Element finded = null;
            foreach (Element element in _elements[i])
            {
                if (element.ColumnIndex == j)
                {
                    finded = element;
                    break;
                }
            }
            if (finded != null)
            {
                return finded.Value;
            }
            else
            {
                return 0d;
            }
        }

        #endregion

        #region Inner Classes
        /// <summary>
        /// Incapsulate information about one matrix elemnt.
        /// It contain 
        /// </summary>
        private class Element
        {
            private double _value;
            private int _columnIndex;
            private int _rowIndex;

            public double Value
            {
                get { return _value; }
                set { _value = value; }
            }

            public int ColumnIndex
            {
                get { return _columnIndex; }
                set { _columnIndex = value; }
            }

            public int RowIndex
            {
                get { return _rowIndex; }
                set { _rowIndex = value; }
            }

            /// <summary>
            /// Used in operations for storing new elements
            /// </summary>
            /// <param name="seriesPercolationThreashold"></param>
            /// <param name="second"></param>
            /// <returns></returns>
            public static bool operator >=(Element first, Element second)
            {
                return first._columnIndex >= second._columnIndex;
            }

            /// <summary>
            /// Used in operations for storing new elements
            /// </summary>
            /// <param name="seriesPercolationThreashold"></param>
            /// <param name="second"></param>
            /// <returns></returns>
            public static bool operator <=(Element first, Element second)
            {
                return first._columnIndex <= second._columnIndex;
            }
        }
        #endregion
    }
}
